/***********************************************/
/**				memory.c by doom119			   */
/**					May, 2010				   */
/***********************************************/
#include <system.h>
//no ramdisk for simple
//suport 16M memory at most for simple

//address below 1M is for OS
#define LOW_MEM 0x100000 // 1M
#define HIGH_MEM 0x1000000 // 16M
#define PAGING_MEMORY (15*1024*1024)
#define PAGING_PAGES (PAGING_MEMORY >> 12) // 15M/4k=3840
#define MAP_NR(addr) (((addr)-LOW_MEM) >> 12)
#define USED 100

#define invalidate() \
	__asm__("movl %%eax, %%cr3\n\t" \
			::"a"(0))

#define copy_page(from, to) \
	__asm__("cld\n\t" \
			"rep\n\t" \
			"movsl\n\t" \
			::"S"(from), "D"(to), "c"(1024)) \
			

static unsigned char mem_map[PAGING_PAGES] = {0,};
extern void _page_fault();
extern void printk(char*);

//get physical address of the first free page, mark it used.
//if no one, return 0
unsigned long get_free_page(void)
{
	register unsigned long _address __asm__("ax");
	__asm__("std\n\t"
			"repne\n\t"
			"scasb\n\t"
			"jne 1f\n\t"
			"movb $1, 1(%%edi)\n\t"
			"sall $12, %%ecx\n\t"
			"addl %4, %%ecx\n\t"
			"movl %%ecx, %%edx\n\t"
			"leal 4092(%%edx), %%edi\n\t"
			"movl $1024, %%ecx\n\t"
			"rep\n\t"
			"stosl\n\t" //is it necessary to clean this page with 0?
			"movl %%edx, %%eax\n\t"
			"1:"
			:"=a"(_address)
			:"a"(0), "c"(PAGING_PAGES), "D"(mem_map+PAGING_PAGES-1), "i"(LOW_MEM)
			:"dx");
	return _address;
}

void free_page(unsigned long addr)
{
	if(addr < LOW_MEM)
		return;
	if(mem_map[MAP_NR(addr)] > 0) { mem_map[addr]--; }
	else 
	{
		//panic("trying to free free page");
	}
}

unsigned long map_address(unsigned long physcal_addr, unsigned long linear_addr)
{
	unsigned long *page_dir, *page_table, tmp;

//	if(physcal_addr < LOW_MEM || physcal_addr > HIGHT_MEM)
//		printk("map_address: physcal_addr wront");
//	if(mem_map[(physcal_addr - LOW_MEM) >> 12] != 1)
//		printk("map_address:trying to put linear_addr to an unused physcal_addr");

	page_dir = (unsigned long*)((linear_addr >> 20) & 0xffc);
	if((*page_dir) & 1)
		page_table = (unsigned long *)((*page_dir) & 0xfffff000);
	else
	{
		if(!(tmp = get_free_page()));
			return 0;
		*page_dir = tmp | 7;
		page_table = (unsigned long *)tmp;
	}
	page_table[(linear_addr >> 12) & 0x3ff] = physcal_addr;
	return physcal_addr;
}

int free_page_tables(unsigned long addr, long size)
{
	unsigned long *page_dir, *page_table, *tmp;
	int i;

	if(addr & 0x3fffff)
		return 1;
	if(!addr)
		return 1;

	page_dir = (unsigned long *)((addr >> 20) & 0xffc);
	size = (size + 0x3fffff) >> 22;
	for(; size-- > 0; ++page_dir)
	{
		if(!(1&(*page_dir)))
			continue;
		page_table = (unsigned long *)((*page_dir) & 0xfffff000);
		for(i = 0; i < 1024; ++i, ++page_table)
		{
			if(1 & (*page_table))
				free_page((unsigned long)(*page_table & 0xfffff000));
			*page_table = 0;
		}
		free_page((unsigned long)page_table);
		*page_dir = 0;
	}
	invalidate();
	return 0;
}

int copy_page_tables(unsigned long from, unsigned long to, long size)
{
	unsigned long *from_page_table, *to_page_table, *from_dir, *to_dir;
	unsigned long this_page;
	int nr;

	from_dir = (unsigned long *)((from >> 20) & 0xffc);
	to_dir = (unsigned long *)(((to >> 20) & 0xffc));
	size = (size + 0x3fffff) >> 22;

	for(; size-- > 0; ++from_dir, ++to_dir)
	{
		if(1 & *to_dir)
		{
		//	panic("copy_page_tables: to_dir exist");
			return -1;
		}
	
		if(!(1&(*from_dir)))
			continue;
		if(!(to_page_table = (unsigned long *)get_free_page()))
			return -1;
		*to_dir = ((unsigned long)to_page_table) | 7;
		from_page_table = (unsigned long *)(*from_dir & 0xfffff000);

		nr = (from == 0) ? 0xa0 : 1024;
		for(; nr-- > 0; ++from_page_table, ++to_page_table)
		{
			this_page = *from_page_table;
			if(!(1 & this_page))
				continue;
			this_page &= ~2;
			*to_page_table = this_page;

			if(this_page > LOW_MEM)
			{
				*from_page_table = this_page;
				mem_map[MAP_NR(this_page)]++;
			}
		}
	}

	invalidate();
	return 0;
}

void get_page(unsigned long linear_addr)
{
	unsigned long tmp;
	if(!(tmp = get_free_page()) || !map_address(tmp, linear_addr))
	{
		free_page(tmp);
	//	oom();
	}
}

void do_no_page(unsigned long addr)
{
	get_page(addr);
}

void do_wp_page(unsigned long addr)
{
	unsigned long *page_table, *page_dir, *page_table_entry;
	unsigned long old_page, new_page;

	page_dir = (unsigned long *)((addr >> 20) & 0xffc);
	page_table = (unsigned long *)(*page_dir & 0xfffff000);
	page_table_entry = (unsigned long *)(page_table + ((addr >> 10) & 0xffc));
	old_page = 0xfffff000 & *page_table_entry;
	if((old_page > LOW_MEM) && mem_map[MAP_NR(old_page)] == 1)
	{
		*page_table_entry |= 2;
		invalidate();
		return;
	}
	if(!(new_page = get_free_page()))
	{
		//oom();
		return;
	}
	if(old_page >= LOW_MEM)
		mem_map[MAP_NR(old_page)]--;
	*page_table_entry = new_page | 7;
	copy_page(old_page, new_page);
	invalidate();
}

void mem_init()
{
	unsigned int nr, i;
	nr = MAP_NR(0x400000);
	for(i = 0; i < nr; ++i)
		mem_map[i] = USED;
	i = MAP_NR(HIGH_MEM);
	for(; nr < i; ++nr)
		mem_map[nr] = 0;
	set_trap_gate(14, &_page_fault);

	printk("** memory OK **");
}
